package top.byfree.jbyan.saber.controller

import cn.dev33.satoken.secure.SaSecureUtil
import cn.dev33.satoken.stp.SaTokenInfo
import cn.dev33.satoken.stp.StpUtil
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.validation.BindException
import org.springframework.validation.FieldError
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import top.byfree.jbyan.saber.bean.SECRET_KEY
import top.byfree.jbyan.saber.bean.USER_STATUS_NORMAL
import top.byfree.jbyan.saber.bean.VerificationCode
import top.byfree.jbyan.saber.bean.dto.LoginDto
import top.byfree.jbyan.saber.bean.dto.RegisterDto
import top.byfree.jbyan.saber.common.*
import top.byfree.jbyan.saber.exception.AuthException
import top.byfree.jbyan.saber.service.EmailService
import top.byfree.jbyan.saber.service.UserService


/**
 * 认证相关控制器
 *
 * @author jbyan
 * @since 21/3/2024
 */

@RestController
@RequestMapping("auth")
class AuthController(
    @Autowired private val userService: UserService,
    @Autowired private val emailService: EmailService,
    @Autowired private val verificationContext: VerificationContext,
    @Value("\${saber.email.expiration}") private val expiration: Int
) {

    /**
     * 发送邮件验证码
     *
     * @param email 邮箱
     */
    @GetMapping("email/{email}")
    fun code(
        @PathVariable email: String
    ): R<Unit> {
        // 邮箱格式校验
        if (email.isValidEmail()) {
            // 存在用户名称
            val be = BindException(email, "top.byfree.jbyan.saberblog.controller.AuthController")
            be.addError(FieldError("String", "email", "邮箱格式错误"))
            throw be
        }
        val createCode = createCode(4)
        // 发送邮箱
        emailService.sendEmail(email, createCode)
        // 保存缓存
        val exp = System.currentTimeMillis() + (expiration * 60 * 1000)
        verificationContext.setCode(email, VerificationCode(createCode, exp, 0))
        return ok()
    }

    /**
     * 退出当前用户登录
     *
     */
    @PostMapping("logout")
    fun logout() = ok(data = StpUtil.logout())


    /**
     * 用户登录接口
     *
     * @param loginDto 登录请求体
     */
    @PostMapping("login")
    fun login(
        @Validated @RequestBody loginDto: LoginDto
    ): R<SaTokenInfo> {
        // 1. 检索用户
        val user = userService.byUsername(loginDto.username)

        user ?: throw AuthException("用户不存在")

        // 2. 校验密码
        val aesEncrypt = SaSecureUtil.aesEncrypt(SECRET_KEY, loginDto.password)

        if (user.password != aesEncrypt) {
            throw AuthException("用户名或者密码不正确")
        }

        // 3. 检测用户状态
        if (user.status != USER_STATUS_NORMAL) {
            throw AuthException("您的用户已被停用，请联系管理员")
        }

        // 4. 登录用户
        StpUtil.login(user.userId)

        // 5. 记录 session
        StpUtil.getSession().set("user", user.toVo())

        // 6. 响应token
        val tokenInfo = StpUtil.getTokenInfo()
        return ok(data = tokenInfo)
    }

    /**
     * 用户注册接口
     *
     * @param registerDto 用户注册请求体
     */
    @PostMapping("register")
    fun register(
        @Validated @RequestBody registerDto: RegisterDto
    ): R<Unit> {

        // 重复用户名称校验
        userService.byUsername(registerDto.username)?.let {
            // 存在用户名称
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            be.addError(FieldError("RegisterDto", "username", "用户名已存在"))
            throw be
        }

        // 重复邮箱校验
        userService.byEmail(registerDto.email)?.let {
            // 存在邮箱
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            be.addError(FieldError("RegisterDto", "email", "邮箱已经被注册"))
            throw be
        }

        // 获取验证码缓存
        val codePojo = verificationContext.getCode(registerDto.email)

        // 缓存不存在
        codePojo ?: {
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            be.addError(FieldError("RegisterDto", "code", "请先获取验证码"))
            throw be
        }

        // 验证码缓存过期
        if (System.currentTimeMillis() > codePojo!!.expireTime) {
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            be.addError(FieldError("RegisterDto", "code", "验证码过期，请重新获取"))
            throw be
        }

        // 验证码缓存重试3次
        if (codePojo.errCount > 2) {
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            be.addError(FieldError("RegisterDto", "code", "验证码错误重试3次，请重新获取"))
            verificationContext.removeCode(registerDto.email)
            throw be
        }

        // 验证码错误
        if (codePojo.code != registerDto.code) {
            val be = BindException(registerDto, "top.byfree.jbyan.saberblog.bean.dto.RegisterDto")
            synchronized(codePojo) {
                codePojo.errCount++
            }
            be.addError(FieldError("RegisterDto", "code", "验证码错误, 您还剩${3 - codePojo.errCount}次重试机会"))
            throw be
        }

        // 创建用户
        return result(userService.add(registerDto))
    }


}